package lib.map;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.StringTokenizer;

import lib.Commands;
import lib.Config;
import lib.Parser;
import lib.Universe;
import lib.Utils;
import lib.map.objects.MapElement;
import lib.player.Player;

public class Map {
	public Config cfg =Config.getInstance();
	Utils util = new Utils();
	public Parser parser;

	private MapElement[][] map = new MapElement[Config.CollumnCount][Config.RowCount];
	public String name;
	public String mapSRC = "default.msrc";
	public int xGrid = 0;
	public int yGrid = 0;
	public int xPart = 30;
	public int yPart = 24;
	private Image background = util
			.imageLodaer("data/textures/space/space.png");
	private Image thetah = util.imageLodaer("data/textures/other/theta.png");
	Color borderColor = new Color(20, 20, 20, 255);
	private Universe parent;
	public Point blackHole = null;

	/**
	 * 
	 * @param cfg
	 *            .xRes
	 * @param cfg
	 *            .yRes
	 * @param mapName
	 */
	public Map(String mapName) {
		name = mapName;
		this.xGrid = cfg.xRes / xPart;
		this.yGrid = cfg.yRes / xPart;
		System.out.println(cfg.xRes + "|" + cfg.yRes + "|" + xGrid + "|"
				+ yGrid + "|" + xPart + "|" + yPart);

		loadMap("maps/" + mapName);
	}

	public Map(Universe uni, String mapName) {
		name = mapName;
		parent = uni;
		this.xGrid = cfg.xRes / xPart;
		this.yGrid = cfg.yRes / xPart;
		System.out.println(cfg.xRes + "|" + cfg.yRes + "|" + xGrid + "|"
				+ yGrid + "|" + xPart + "|" + yPart);

		loadMap("maps/" + mapName);
	}

	public void setMapResolution(int x, int y) {
		cfg.xRes = x;
		cfg.yRes = y;
		this.xGrid = cfg.xRes / xPart;
		this.yGrid = cfg.yRes / xPart;
	}

	public Dimension getMapResolution() {
		return new Dimension(cfg.xRes, cfg.yRes);
	}

	/**
	 * Mehtode to check if player got a map Collision
	 * 
	 * @param Col
	 *            The Column of the map 0-100
	 * @param Row
	 *            The Row of the map 0-100
	 * @return true if move is not allowed
	 */
	public boolean checkCollision(int Col, int Row, int hit) {
		Col = Col / 10;
		Row = Row / 10;
		boolean collision = false;
		int widh = 1;
		int hight = 1;
		if (Config.debug)
			System.out.print("\nCollision: Column: " + Col + " Row: " + Row
					+ " ");
		if (Col < 0 || Col + 1 >= Config.CollumnCount)
			return true;
		if (Row < 0 || Row + 1 >= Config.RowCount)
			return true;
		if (Config.debug)
			System.out.print(map[Col][Row].Block);
		if (check(Col, Row, hit))
			collision = true;
		if (check(Col + widh, Row, hit))
			collision = true;
		if (check(Col, Row + hight, hit))
			collision = true;
		if (check(Col + widh, Row + hight, hit))
			collision = true;
		return collision;
	}

	public boolean checkPickable(int Col, int Row) {
		return map[Col / 10][Row / 10].pickable;
	}

	private boolean check(int Col, int Row, int hit) {
		if (map[Col][Row].Block) {
			map[Col][Row].hitpoints -= hit;
			if (map[Col][Row].hitpoints <= 0) {
				parent.map.setField(0, Col, Row);
				parent.broadcast(Commands.SETMAP, "false 0 " + Col + " " + Row);
			}
			return true;
		}
		return false;

	}

	public MapElement getField(int Col, int Row) {
		return map[Col][Row];
	}

	/**
	 * Draws the MiniMap
	 * 
	 * @param g
	 *            Graphics
	 * @param resizeFactor
	 *            float to set the resize of the map
	 * @return Graphics
	 */
	public Graphics2D drawMiniMap(Graphics2D g, float resizeFactor) {
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		g.setColor(new Color(10, 10, 10, 150));// TODO Minimap is to dark
		g.fillRect(0, 0, (int) (cfg.xRes * resizeFactor),
				(int) (cfg.yRes * resizeFactor));
		for (int Row = 0; Row < Config.RowCount; Row++) {
			for (int Col = 0; Col < Config.CollumnCount; Col++) {
				g.setColor(map[Col][Row].color);
				g
						.fillRect(
								(int) (Col * cfg.xRes / Config.CollumnCount * resizeFactor),
								(int) (Row * cfg.yRes / Config.RowCount * resizeFactor),
								(int) (cfg.xRes / Config.CollumnCount * resizeFactor),
								(int) (cfg.yRes / Config.RowCount * resizeFactor));
			}
		}
		return g;
	}

	/**
	 * Draws a part around the Column and row :P
	 * 
	 * @param g
	 * @param Col
	 * @param Row
	 * @return Graphics
	 */
	public void  drawPart(Graphics2D g, Player pl, int phase) {
		int Col = (int) pl.xPos;
		int Row = (int) pl.yPos;
		int xOffset = 10 - Col % 10;
		int yOffset = 10 - Row % 10;
		if (xOffset < 0)
			xOffset = 9999;
		if (yOffset < 0)
			yOffset = 9999;
		Col = Col / 10;
		Row = Row / 10;
		g.drawImage(background, 0, 0, cfg.xRes, cfg.yRes, null);
		Row = Row - yPart / 2;
		Col = Col - xPart / 2;
		if (Col < 0) {
			Col = 0;
			xOffset = 9999;
		}
		if (Col + xPart >= Config.CollumnCount) {
			Col = Config.CollumnCount - xPart;
			xOffset = 9999;
		}
		if (Row < 0) {
			Row = 0;
			yOffset = 9999;
		}
		if (Row + yPart > Config.RowCount) {
			Row = Config.RowCount - yPart;
			yOffset = 9999;
		}
		Image img;
		for (int _coll = 0; _coll < xPart; ++_coll) {
			for (int _row = 0; _row < yPart; ++_row) {
				img = map[Col + _coll][Row + _row].getTexture(phase);
				if (img != null)
					g.drawImage(img, xGrid * _coll - xGrid / xOffset, yGrid
							* _row - yGrid / yOffset, xGrid, yGrid, null);
			}
		}
	drawBorders(g);
	}

	/**
	 * Draws the Border and title
	 * 
	 * @param g
	 * @return
	 */

	private void drawBorders(Graphics2D g) {
		g.setColor(borderColor);
		// draw top
		g.fillRect(0, 0, cfg.xRes, cfg.yRes / yPart);
		// draw Title
		g.setColor(Config.fontColor);
		g.drawString("Project", cfg.xRes / 2 - 50, cfg.yRes / yPart / 2);
		g.setColor(borderColor);
		g.drawImage(thetah, cfg.xRes / 2 + 30, -5, cfg.xRes / xPart, cfg.yRes
				/ yPart, null);
		// draw bottom
		g.fillRect(0, (int) (cfg.yRes - cfg.yRes / 24f * 6f), cfg.xRes,
				(int) (cfg.yRes / 24f * 6f));
		// draw left
		g.fillRect(0, 0, cfg.xRes / xPart, cfg.yRes - cfg.yRes / 5);
		// draw right
		g.fillRect(cfg.xRes - cfg.xRes / xPart, 0, cfg.xRes / xPart, cfg.yRes);
	}

	/**
	 * Loads a map
	 * 
	 * @param mapName
	 *            String representing the name of the map
	 */
	private void loadMap(String mapName) {
		if (mapName.equals("maps/") || !new File(mapName).exists()) {
			parser = new Parser(new File(mapSRC));
			for (int Row = 0; Row < Config.RowCount; Row++) {
				for (int Col = 0; Col < Config.CollumnCount; Col++) {
					map[Col][Row] = parser.mapList.get(0);
				}
			}
			map[50][50] = parser.mapList.get(2);
			blackHole = new Point(500, 500);
			return;

		}

		BufferedReader in = null;
		try {
			FileReader infile = new FileReader(mapName);
			in = new BufferedReader(infile);
			String line;
			StringTokenizer st;
			mapSRC = in.readLine().trim();
			parser = new Parser(new File(mapSRC));
			for (int Row = 0; Row < Config.RowCount; Row++) {
				line = in.readLine();
				st = new StringTokenizer(line, " ");
				for (int Col = 0; Col < Config.CollumnCount; Col++) {
					map[Col][Row] = parser.mapList.get(st.nextToken());
					if (map[Col][Row].NR == 2)
						blackHole = new Point(Col * 10, Row * 10);
				}
			}

		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	/**
	 * Sets a single field of the Map
	 * 
	 * @param obj
	 *            String representing a MapElement
	 * @param Col
	 * @param Row
	 */
	public void setField(int obj, int Col, int Row) {
		map[Col][Row] = parser.mapList.get(obj);
	}

	public String toString() {
		String out = "";
		out += (mapSRC + "\n");
		for (int i = 0; i < Config.RowCount; i++) {
			for (int j = 0; j < Config.CollumnCount; j++) {
				out += (map[j][i].NR + " ");
			}
			out += ("\n");
		}
		return out;
	}

	public void saveMap(String dest) {
		try {
			FileWriter fileout = new FileWriter(dest);
			BufferedWriter out = new BufferedWriter(fileout);
			out.write(this.toString());
			out.close();
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}

	}

	/**
	 * Creates a "mutated" MapElemts (inplace) in Cellular Automaton style. So
	 * we can have a parallel universe that was created by logic. <br>
	 * This is also a allusion to how Stephen Wolfram (A New Kind of Science)
	 * sees the universe. He believes the universe to be a cellular automaton.
	 * And that things in the universe change by applying the rules of such a
	 * cellular automaton.<br>
	 * We don't use the automaton in Wolframs way but we create parallel
	 * universes in a analogical way.
	 * */
	public void createParallelUniverseMap() {
		// TODO use this function
		boolean b[][] = makeStoneArray();
		CellularAutomaton ca = new CellularAutomaton(b);
		b = ca.calcNextGeneration(b);
		map = makeStoneMap(b, map);

	}

	/**
	 * Makes out of all STONEs a boolean array.
	 */
	private boolean[][] makeStoneArray() {
		boolean[][] b = new boolean[map.length][map[0].length];
		MapElement m = null;
		for (int i = 0; i < map.length; i++) {
			for (int j = 0; j < map[0].length; j++) {
				m = map[i][j];
				if (m.NR == 1) {
					b[i][j] = true;
				}
			}
		}
		return b;
	}

	/**
	 * Makes STONEs at the place where there were trues in b. Contrary of
	 * makeStoneArray().
	 */
	private MapElement[][] makeStoneMap(boolean[][] b, MapElement[][] mes) {
		for (int i = 0; i < b.length; i++) {
			for (int j = 0; j < b[0].length; j++) {
				if (mes[i][j].NR == 1) {
					mes[i][j] = parser.mapList.get(0);
				}
				if (b[i][j] && mes[i][j].NR == 0) {
					mes[i][j] = parser.mapList.get(1);
				}
			}
		}
		return mes;
	}

	public void setParser(String s) {
		parser = new Parser(s);
		this.mapSRC = s;
	}

	public void setMapSRC(String mapSrc2) {
		mapSRC = mapSrc2;
		parser = new Parser(new File(mapSrc2));
		for (int Row = 0; Row < Config.RowCount; Row++) {
			for (int Col = 0; Col < Config.CollumnCount; Col++) {
				map[Col][Row] = parser.mapList.get(map[Col][Row].NR);
			}
		}

	}

}
